/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.server.search.view;

import java.io.IOException;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.ResourceBundle;
import javax.el.MethodExpression;
import javax.el.ValueExpression;
import javax.faces.component.UIComponent;
import javax.faces.component.UIParameter;
import javax.faces.component.html.HtmlColumn;
import javax.faces.component.html.HtmlCommandButton;
import javax.faces.component.html.HtmlCommandLink;
import javax.faces.component.html.HtmlDataTable;
import javax.faces.component.html.HtmlForm;
import javax.faces.component.html.HtmlGraphicImage;
import javax.faces.component.html.HtmlInputHidden;
import javax.faces.component.html.HtmlOutputLink;
import javax.faces.component.html.HtmlOutputText;
import javax.faces.component.html.HtmlPanelGroup;
import javax.faces.context.FacesContext;
import javax.faces.event.ActionEvent;
import javax.faces.event.MethodExpressionActionListener;
import javax.jcr.Node;
import javax.jcr.RepositoryException;
import javax.jcr.Session;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import offset.nodes.Constants;
import offset.nodes.server.controller.AttributeConstants;
import offset.nodes.server.dispatch.controller.DispatchAttribute;
import offset.nodes.server.view.ContainerBean;
import offset.nodes.server.view.list.ListManager;
import offset.nodes.server.view.list.ListStyles;
import offset.nodes.server.view.list.TextNodeConverter;
import offset.nodes.server.search.model.SearchCache;
import offset.nodes.server.search.model.SearchModel;
import offset.nodes.server.search.model.SearchType;
import offset.nodes.server.search.controller.QueryAttribute;
import offset.nodes.server.version.view.VersionBean;
import offset.nodes.server.view.FacesUtils;
import offset.nodes.server.view.component.applet.AppletComponent;
import offset.nodes.server.view.component.applet.AppletModel;

/**
 *
 * @author Walter Lütgenau
 */
public class SearchBean extends ContainerBean {

    public static final String ATT_SORT_COLUMN = "sortColumn";
    public static final String CONTENT_TYPE = "search";
    static ResourceBundle bundle = ResourceBundle.getBundle("offset.nodes.server.view.ApplicationResource");
    String key;
    QueryListManager listManager;
    
    public ListManager getListManager() {
        QueryListManager queryListManager = getQueryListManager();
        if (queryListManager != null)
            return queryListManager;

        return new BeforeUpdateModelQueryListManager();
    }

    protected QueryListManager getQueryListManager() {
        init();
        return listManager;
    }

    protected void init() {
        QueryAttribute attribute = (QueryAttribute) ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).getAttribute(QueryAttribute.ATT_QUERY);
        if ( attribute != null) {
            key = SearchCache.getInstance(FacesUtils.getRequest()).addSearch(attribute.getQueryType(), attribute.getQuery());
            ((HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest()).removeAttribute(QueryAttribute.ATT_QUERY);
        }

        if (key != null && listManager == null)
            listManager = new QueryListManager(key);
    }

    public void sort(ActionEvent event) {
        String column = (String) event.getComponent().getAttributes().get(ATT_SORT_COLUMN);        
        getQueryListManager().getSortingInfo().columnClicked(column);
    }
    
    public void renderXPath(ActionEvent e) throws RepositoryException, IOException {
        AppletModel appletModel = (AppletModel) getComponents().get("xpath");
        appletModel.setRendered(true);
        //FacesUtils.refresh();
    }
    
    public void renderEdit(ActionEvent e) {
        AppletModel appletModel = (AppletModel) getComponents().get("edit");
        appletModel.setRendered(true);
    }

    public void renderTemporaryEdit(ActionEvent e) throws RepositoryException, IOException {
        AppletModel appletModel = (AppletModel) getComponents().get("temporaryEdit");
        appletModel.setRendered(true);
    }

    public void renderSaveAs(ActionEvent e) throws RepositoryException, IOException {
        AppletModel appletModel = (AppletModel) getComponents().get("saveAs");
        appletModel.setRendered(true);
    }
    
    protected void initComponents() {
        AppletModel xpathModel = new AppletModel();
        xpathModel.setRendered(false);
        getComponents().put("xpath", xpathModel);
 
        AppletModel editModel = new AppletModel();
        editModel.setRendered(false);
        getComponents().put("edit", editModel);

        AppletModel temporaryEditModel = new AppletModel();
        editModel.setRendered(false);
        getComponents().put("temporaryEdit", temporaryEditModel);

        AppletModel saveAsModel = new AppletModel();
        editModel.setRendered(false);
        getComponents().put("saveAs", saveAsModel);
    }

    public String getKey() {
        return key;
    }

    public void setKey(String key) {
        this.key = key;
    }
    
    protected HtmlPanelGroup createAppletAction() {
         // Create group.
        HtmlPanelGroup group = new HtmlPanelGroup();
        
        HtmlCommandButton button = new HtmlCommandButton();
        button.addActionListener(new MethodExpressionActionListener(createMethodExpression("#{SearchBean.renderNewNode}", null, new Class[]{ActionEvent.class})));
        button.setValue("Click here");
        //button.setId("newNode");
        button.setStyleClass("submit");
        
        AppletComponent comp = new AppletComponent();
        comp.getAttributes().put(AppletComponent.PAR_HEIGHT, "0");
        comp.getAttributes().put(AppletComponent.PAR_WIDTH, "0");
        comp.getAttributes().put(AppletComponent.PAR_CODE, "offset.nodes.dialog.newnode.view.NewNodeApplet");
        comp.getAttributes().put(AppletComponent.PAR_JAR, "dialog.jar");
        HttpServletRequest request = (HttpServletRequest) FacesContext.getCurrentInstance().getExternalContext().getRequest();
        String codebase = FacesUtils.getContextPath(request);
        comp.getAttributes().put(AppletComponent.PAR_CODEBASE, codebase);
        comp.setValueExpression("rendered", createValueExpression("#{SearchBean.components.newNode.rendered}", Boolean.class));        
        
        UIParameter par = new UIParameter();
        par.setName("service");
        par.setValueExpression("value", createValueExpression("#{NodesBean.dialogPath}", String.class));
        comp.getChildren().add(par);
        
        par = new UIParameter();
        par.setName("base");
        par.setValueExpression("value", createValueExpression("#{NodesBean.repositoryPath}", String.class));
        comp.getChildren().add(par);
        
        par = new UIParameter();
        par.setName("path");
        par.setValue("");
        comp.getChildren().add(par);
        
        HtmlForm form = new HtmlForm();
        form.getChildren().add(button);
        form.getChildren().add(comp);
        
        group.getChildren().add(form);
        
        return group;
    }
    
    // Actions -----------------------------------------------------------------------------------
    private HtmlPanelGroup createDataTable() throws RepositoryException {
         // Create group.
        HtmlPanelGroup group = new HtmlPanelGroup();

        // Page start
        HtmlInputHidden pageStart = new HtmlInputHidden();
        pageStart.setValueExpression("value",
                createValueExpression("#{SearchBean.listManager.pageStart}", Integer.class));

        group.getChildren().add(pageStart);

        // Create datatable.
        HtmlDataTable dataTable = new HtmlDataTable();
        dataTable.setValueExpression("value",
                createValueExpression("#{SearchBean.listManager.result}", List.class));
        dataTable.setVar("node");
        dataTable.setRowClasses(ListStyles.CLASSES_ROW);
        dataTable.setHeaderClass(ListStyles.CLASS_HEADER);
        dataTable.setStyleClass(ListStyles.CLASS_CONTENT);
        dataTable.setCaptionClass(ListStyles.CLASS_CAPTION);

        // caption
        HtmlOutputText caption = new HtmlOutputText();
        caption.setValue(bundle.getString("searchResults"));
        dataTable.getFacets().put("caption", caption);

        HashSet<String> referenceColumns = new HashSet<String>();
        HashMap<String,String> enumeratedColumns = new HashMap<String, String>();
        HashMap<String,Integer> booleanColumns = new HashMap<String,Integer>();

        if ( getQueryListManager().getQueryType() == SearchType.xpath) {
            HttpSession session = (HttpSession) FacesContext.getCurrentInstance().getExternalContext().getSession(false);
            Session repositorySession = (Session) session.getAttribute(AttributeConstants.ATT_REPOSITORY_SESSION);
            if ( repositorySession != null) {
                SearchModel model = new SearchModel(repositorySession);
                referenceColumns = model.getReferenceColumns(getQueryListManager().getQuery());
                enumeratedColumns = model.getEnumeratedColumns(getQueryListManager().getQuery());
                booleanColumns = model.getBooleanColumns(getQueryListManager().getQuery());
            }
        }
        String[] columnNames = getQueryListManager().getColumnInfo().getSelectedColumns();
        
        for (int i = 0; i < columnNames.length; i++) {
            // Create column and add it to datatable.
            HtmlColumn column = new HtmlColumn();

            HtmlCommandLink header = (HtmlCommandLink) FacesContext.getCurrentInstance().getApplication().createComponent(HtmlCommandLink.COMPONENT_TYPE);
            header.getAttributes().put(ATT_SORT_COLUMN, columnNames[i]);
            header.addActionListener(new MethodExpressionActionListener(createMethodExpression("#{SearchBean.sort}", null, new Class[]{ActionEvent.class})));
            header.setStyleClass(ListStyles.CLASS_HEADER);
            header.setId("mylink"+i); // seems to be necessary for unknown reasons. I consider it a bug.

            HtmlOutputText outputHeader = new HtmlOutputText();
            outputHeader.setValue(columnNames[i]);
            header.getChildren().add(outputHeader);
            
            HtmlGraphicImage image = new HtmlGraphicImage();
            image.setValueExpression("value", createValueExpression("#{SearchBean.listManager.sortingInfo.ascending['" + columnNames[i] + 
                    "'] ? '/resources/sort_ascending.png' : '/resources/sort_descending.png'}", String.class));
            image.setValueExpression("rendered", createValueExpression("#{SearchBean.listManager.sortingInfo.firstColumn['" + columnNames[i]+ "']}", Boolean.class));
            header.getChildren().add(image);

            column.setHeader(header);

            if ( columnNames[i].equals(Constants.JCR_PATH) ) {
                HtmlOutputLink link = new HtmlOutputLink();
                link.setValueExpression("value",
                        createValueExpression("#{NodesBean.repositoryPath}#{node.viewReference}", String.class));
                column.getChildren().add(link);

                HtmlOutputText output = new HtmlOutputText();
                output.setValueExpression("value",
                        createValueExpression("#{node.columns[\"" + columnNames[i] + "\"]}", String.class));
                output.setStyleClass(ListStyles.CLASS_CONTENT);
                link.getChildren().add(output);
            }
            else if ( columnNames[i].equals(Constants.JCR_PRIMARY_TYPE) ) {
                HtmlOutputLink link = new HtmlOutputLink();
                link.setValueExpression("value",
                        createValueExpression("#{NodesBean.repositoryPath}#{node.dataReference}", String.class));
                column.getChildren().add(link);

                HtmlOutputText output = new HtmlOutputText();
                output.setValueExpression("value",
                        createValueExpression("#{node.columns[\"" + columnNames[i] + "\"]}", String.class));
                output.setStyleClass(ListStyles.CLASS_CONTENT);
                link.getChildren().add(output);
            }
            else if ( referenceColumns.contains(columnNames[i]) ) {
                HtmlOutputLink link = new HtmlOutputLink();
                link.setValueExpression("value",
                        createValueExpression("#{NodesBean.repositoryPath}?" + AttributeConstants.ATT_DISPATCHER_REFERENCE + "=#{node.columns[\"" + columnNames[i] + "\"]}", String.class));
                column.getChildren().add(link);

                HtmlOutputText output = new HtmlOutputText();
                output.setValueExpression("value",
                        createValueExpression("#{node.columns[\"" + columnNames[i] + "\"]}", String.class));
                output.setStyleClass(ListStyles.CLASS_CONTENT);
                link.getChildren().add(output);
             
            }
            else if (enumeratedColumns.get(columnNames[i]) != null) {
                image = new HtmlGraphicImage();
                image.setValueExpression("value",
                        createValueExpression("#{NodesBean.repositoryPath}?nodeType="
                        + enumeratedColumns.get(columnNames[i]) + "&propertyName="
                        + columnNames[i] + "&propertyValue=#{node.columns[\""
                        + columnNames[i] + "\"]}&contentType="
                        + Constants.CONTENT_TYPE_ICON, String.class));
                image.setValueExpression("title", createValueExpression("#{node.columns[\"" + columnNames[i] + "\"]}", String.class));

                column.getChildren().add(image);
            }
            else if (booleanColumns.get(columnNames[i]) != null) {
                image = new HtmlGraphicImage();
                image.setValueExpression("value",
                        createValueExpression("#{NodesBean.repositoryPath}?propertyType="
                        + booleanColumns.get(columnNames[i]).intValue()
                        + "&propertyValue=#{node.columns[\""
                        + columnNames[i] + "\"]}&contentType="
                        + Constants.CONTENT_TYPE_ICON, String.class));
                image.setValueExpression("title", createValueExpression("#{node.columns[\"" + columnNames[i] + "\"]}", String.class));

                column.getChildren().add(image);
            }
            else {
                HtmlOutputText output = new HtmlOutputText();
                output.setValueExpression("value",
                        createValueExpression("#{node.columns[\"" + columnNames[i] + "\"]}", String.class));
                output.setStyleClass(ListStyles.CLASS_CONTENT);
                column.getChildren().add(output);
            }

            dataTable.getChildren().add(column);
        }
        
        dataTable.setFooter(createPagingControls("SearchBean"));

        // Add datatable to group.
        group.getChildren().add(dataTable);
        
        return group;
    }
    
    // Actions -----------------------------------------------------------------------------------
    private HtmlPanelGroup createTextTable() throws RepositoryException {
         // Renew group.
        HtmlPanelGroup group = new HtmlPanelGroup();

        // Page start
        HtmlInputHidden pageStart = new HtmlInputHidden();
        pageStart.setValueExpression("value",
                createValueExpression("#{SearchBean.listManager.pageStart}", Integer.class));

        group.getChildren().add(pageStart);

        // Create datatable.
        HtmlDataTable dataTable = new HtmlDataTable();
        dataTable.setValueExpression("value",
                createValueExpression("#{SearchBean.listManager.result}", List.class));
        dataTable.setVar("Row");

        // Create column and add it to datatable.
        HtmlColumn column = new HtmlColumn();

        HtmlOutputText output = new HtmlOutputText();
        output.setEscape(false);
        output.setValue("<dl><dt>");
        column.getChildren().add(output);
        
        HtmlOutputLink link = new HtmlOutputLink();
        link.setValueExpression("value", createValueExpression("#{NodesBean.repositoryPath}#{Row.path}", String.class));
        output = new HtmlOutputText();
        output.setValueExpression("value",createValueExpression("#{Row.name}", String.class));
        link.getChildren().add(output);
        column.getChildren().add(link);
        
        output = new HtmlOutputText();
        output.setValue("</dt><dd>");
        output.setEscape(false);
        column.getChildren().add(output);
        
        output = new HtmlOutputText();
        output.setEscape(false);
        output.setConverter(new TextNodeConverter());
        output.setValueExpression("value",
                createValueExpression("#{Row}", Node.class));
        column.getChildren().add(output);
        
        output = new HtmlOutputText();
        output.setValue("</dd><dd><em>");
        output.setEscape(false);
        column.getChildren().add(output);
        
        output = new HtmlOutputText();
        output.setValueExpression("value", createValueExpression("#{Row.path}", String.class));
        column.getChildren().add(output);
        
        output = new HtmlOutputText();
        output.setValue("</em></dd></dl>");
        output.setEscape(false);
        column.getChildren().add(output);
        

        dataTable.getChildren().add(column);
        
        dataTable.setFooter(createPagingControls("SearchBean"));

        // Add datatable to group.
        group.getChildren().add(dataTable);
        
        return group;
    }

    protected UIComponent createPagingControls(String beanName) {
        HtmlPanelGroup footer = new HtmlPanelGroup();
        footer.setValueExpression("rendered", createValueExpression("#{" + beanName + ".listManager.pager.pagingRequired}", Boolean.class));
        
        HtmlCommandLink first = new HtmlCommandLink();
        HtmlGraphicImage image = new HtmlGraphicImage();
        image.setValueExpression("value", createValueExpression("#{NodesBean.imagePath}/first.png", String.class));
        first.getChildren().add(image);
        first.setActionExpression(createMethodExpression("#{" + beanName + ".listManager.pager.first}", null, new Class[0]));
        
        HtmlCommandLink previous = new HtmlCommandLink();
        image = new HtmlGraphicImage();
        image.setValueExpression("value", createValueExpression("#{NodesBean.imagePath}/previous.png", String.class));
        previous.getChildren().add(image);
        previous.setActionExpression(createMethodExpression("#{" + beanName + ".listManager.pager.previous}", null, new Class[0]));
        
        HtmlCommandLink next = new HtmlCommandLink();
        image = new HtmlGraphicImage();
        image.setValueExpression("value", createValueExpression("#{NodesBean.imagePath}/next.png", String.class));
        next.getChildren().add(image);
        next.setActionExpression(createMethodExpression("#{" + beanName + ".listManager.pager.next}", null, new Class[0]));
        
        HtmlCommandLink last = new HtmlCommandLink();
        image = new HtmlGraphicImage();
        image.setValueExpression("value", createValueExpression("#{NodesBean.imagePath}/last.png", String.class));
        last.getChildren().add(image);
        last.setActionExpression(createMethodExpression("#{" + beanName + ".listManager.pager.last}", null, new Class[0]));
        
        footer.getChildren().add(first);
        HtmlOutputText output = new HtmlOutputText();
        output.setValue(" ");
        footer.getChildren().add(output);
        
        footer.getChildren().add(previous);
        output = new HtmlOutputText();
        output.setValue(" ");
        footer.getChildren().add(output);
        
        footer.getChildren().add(next);
        output = new HtmlOutputText();
        output.setValue(" ");
        footer.getChildren().add(output);
        
        footer.getChildren().add(last);
        output = new HtmlOutputText();
        output.setValue(" ");
        footer.getChildren().add(output);
                
        return footer;
    }

    public String refresh() throws RepositoryException {
        if (getQueryListManager() == null)
            return "";
        
        getQueryListManager().setQueryType(SearchType.text);
        if ( getQueryListManager().isEmpty() )
            return "";
        
        dataTableGroup = null;
        
        DispatchAttribute dispatchInfo = new DispatchAttribute();
        dispatchInfo.setPath(FacesUtils.getPath());
        dispatchInfo.setContentType(SearchBean.CONTENT_TYPE);
        
        FacesUtils.getRequest().setAttribute(DispatchAttribute.ATT_DISPATCH, dispatchInfo);
        
        return "search";
    }
    
    public String getQuery() {
        if ( getQueryListManager() == null
                || getQueryListManager().getQueryType() == SearchType.xpath
                || getQueryListManager().getQueryType() == SearchType.sql)
            return "";
        return getQueryListManager().getQuery();
    }
    
    public boolean isXPathQuery() {
        return getQuery().length() == 0;
    }
    
    public void setQuery(String query) {
        if (query.length() == 0)
            return;

        listManager = new QueryListManager();
        getQueryListManager().setQuery(query);
        getQueryListManager().setQueryType(SearchType.text);
        key = SearchCache.getInstance(FacesUtils.getRequest()).addSearch(SearchType.text, query);    
    }

    // dataTableGroup -----------------------------------------------------------------------------------
    private HtmlPanelGroup dataTableGroup; 

    public HtmlPanelGroup getDataTableGroup() throws RepositoryException {
        // This will be called once in the first RESTORE VIEW phase.
        if (dataTableGroup == null ) {
            if ( getQueryListManager().getQueryType() == SearchType.xpath || getQueryListManager().getQueryType() == SearchType.sql)
                dataTableGroup = createDataTable(); // Populate datatable.
            else
                dataTableGroup = createTextTable(); // Populate texttable
        }
        return dataTableGroup;
    }

    public void setDataTableGroup(HtmlPanelGroup dataTableGroup) {
        this.dataTableGroup = dataTableGroup;
    }

    // appletActionGroup -----------------------------------------------------------------------------------
    private HtmlPanelGroup appletActionGroup; 

    public HtmlPanelGroup getAppletActionGroup() throws RepositoryException {
        // This will be called once in the first RESTORE VIEW phase.
        if (appletActionGroup == null || getQueryListManager().isNewQuery()) {
            appletActionGroup = createAppletAction();
        }
        return appletActionGroup;
    }

    public void setAppletActionGroup(HtmlPanelGroup appletActionGroup) {
        this.appletActionGroup = appletActionGroup;
    }

    // Helpers -----------------------------------------------------------------------------------
    private ValueExpression createValueExpression(String valueExpression, Class<?> valueType) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        return facesContext.getApplication().getExpressionFactory().createValueExpression(
                facesContext.getELContext(), valueExpression, valueType);
    }

    private MethodExpression createMethodExpression(String actionExpression, Class<?> returnType, Class[] parameters) {
        FacesContext facesContext = FacesContext.getCurrentInstance();
        return facesContext.getApplication().getExpressionFactory().createMethodExpression(
                facesContext.getELContext(), actionExpression, returnType, parameters);
    }

    public static void main(String[] arguments) {
        System.out.println(SearchBean.class.getSimpleName());
    }
}
